//#define OFFLINE
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Xeml.Document.Contracts;
using Xeml.Document;


using System.Windows.Forms;
using System.Data;
using System.Drawing;
using Xeml.Manager;
using Mpi.Common.Collections;
using XemlUtilities.Indexing;
using System.Diagnostics;
using Mpi.Common.Controls;

namespace XemlInteractiveDesigner
{
    internal class FileManager : IBusyReporter
    {
        private List<IValidator> validators;
        //private IdentifiableObjectDictionary<string, IParameterValidator> paramResources;
        private Timer _timer;
        private Stack<string> _undoStack;
        private Stack<string> _redoStack;

        public ValidationLog.LogDataTable ValidationLog  { get; private set; }

        public bool UnsavedChanges { get; private set; }

        public bool InvalidDocument { get; private set; }

        public bool HasErrors { get; private set; }

        public string FilePath { get; private set; }

        public IDocument CurrentXemlDoc { get; private set; }

        public int LogErrorCount
        {
            get
            {
                return ValidationLog.Select("SeverityCode='Error'").Length;
            }
        }

        public int LogWarningCount
        {
            get
            {
                return ValidationLog.Select("SeverityCode='Warning'").Length;
            }
        }

        public int LogInfoCount
        {
            get
            {
                return ValidationLog.Select("SeverityCode='Info'").Length;
            }
        }

        public FileManager()
        {
            InitDirectories();
            ValidatorManager.Instance.ToString();
            InitValidator();
            //this.dummyXemlDoc = new Xeml.Document.XemlDocument();

            this.ValidationLog = new ValidationLog.LogDataTable();
            this.ValidationLog.LogRowChanged += new ValidationLog.LogRowChangeEventHandler(validationLog_LogRowChanged);
            this.ValidationLog.TableCleared += new DataTableClearEventHandler(validationLog_TableCleared);

            _timer = new Timer();
            _timer.Interval = 500;
            _timer.Tick += new EventHandler(_timer_Tick);
            _undoStack = new Stack<string>(5);
            _redoStack = new Stack<string>(5);


            //validationLog.Columns.Add("SeverityIcon", typeof(Icon));
            //validationLog.Columns.Add("Severity", typeof(string));
            //validationLog.Columns.Add("Message", typeof(string));
            //validationLog.Columns.Add("RelatedObject", typeof(string));

        }

        void _timer_Tick(object sender, EventArgs e)
        {
            Debug.WriteLine("FileMgr: Doc changed tick");
            _timer.Stop();

            CurrentXemlDoc.RefreshXeml();
            if (_undoStack.Count == 5)
            {
                ShiftUndoStack();
            }
            _undoStack.Push(CurrentXemlDoc.Xeml);

            if (!UnsavedChanges && SomethingChanged != null)
            {
                this.UnsavedChanges = true;
                SomethingChanged(this, new EventArgs());
            }

            if (UndoAvailabilityChanged != null)
                UndoAvailabilityChanged(this, new UndoRedoAvailabilityEventArgs(true));
        }

        private void ShiftUndoStack()
        {
            Stack<string> temp = new Stack<string>(5);
            while (_undoStack.Count > 0)
            {
                temp.Push(_undoStack.Pop());
            }
            temp.Pop(); //remove the last item from _undoStack
            while (temp.Count > 0)
            {
                _undoStack.Push(temp.Pop());
            }
        }

        public void Undo()
        {
            if (_undoStack.Count > 0)
            {
                OnBackgroundWorkStarted("Performing undo action.");
                try
                {
                    _redoStack.Push(_undoStack.Pop());

                    CurrentXemlDoc.Changed -= OnCurrentXemlDocChanged;
                    this.SetXemlCode(_undoStack.Peek(), false, false);
                    CurrentXemlDoc.Changed += OnCurrentXemlDocChanged;

                    if (_undoStack.Count < 2)
                    {
                        if (UndoAvailabilityChanged != null)
                            UndoAvailabilityChanged(this, new UndoRedoAvailabilityEventArgs(false));
                    }


                    if (RedoAvailabilityChanged != null)
                        RedoAvailabilityChanged(this, new UndoRedoAvailabilityEventArgs(true));
                }
                finally
                {
                    OnBackgroundWorkEnded();
                }
            }
        }

        public void Redo()
        {
            if (_redoStack.Count > 0)
            {
                OnBackgroundWorkStarted("Performing redo action.");
                try
                {
                    _undoStack.Push(_redoStack.Pop());

                    CurrentXemlDoc.Changed -= OnCurrentXemlDocChanged;
                    this.SetXemlCode(_undoStack.Peek(), false, false);
                    CurrentXemlDoc.Changed += OnCurrentXemlDocChanged;

                    if (UndoAvailabilityChanged != null)
                        UndoAvailabilityChanged(this, new UndoRedoAvailabilityEventArgs(true));

                    if (_redoStack.Count < 1)
                    {
                        if (RedoAvailabilityChanged != null)
                            RedoAvailabilityChanged(this, new UndoRedoAvailabilityEventArgs(false));
                    }
                }
                finally
                {
                    OnBackgroundWorkEnded();
                }
            }
        }

        /// <summary>
        /// Initializes neccessary directories and files
        /// </summary>
        public void InitDirectories()
        {
            if (string.IsNullOrEmpty(Properties.Settings.Default.StoreFolder))
            {
                string pstore = Path.GetFullPath(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
                Properties.Settings.Default.StoreFolder = Path.Combine(pstore, "XEMLStore");
                Properties.Settings.Default.Save();
            }
            if (string.IsNullOrEmpty(Properties.Settings.Default.TemplateFolder))
            {
                string ptempl = Path.GetFullPath(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
                Properties.Settings.Default.TemplateFolder = Path.Combine(ptempl, "XEMLStore" + Path.DirectorySeparatorChar + "Templates");
                Properties.Settings.Default.Save();
            }
            if (!Directory.Exists(@Properties.Settings.Default.StoreFolder))
            {
                Directory.CreateDirectory(@Properties.Settings.Default.StoreFolder);
            }
            if (!Directory.Exists(@Properties.Settings.Default.TemplateFolder))
            {
                Directory.CreateDirectory(@Properties.Settings.Default.TemplateFolder);
            }

            if (!File.Exists(Path.Combine(Properties.Settings.Default.TemplateFolder, "Standard.xeml")))
            {
                File.WriteAllBytes(Path.Combine(Properties.Settings.Default.TemplateFolder, "Standard.xeml"), MainResources.Standard);
            }
        }

        void validationLog_TableCleared(object sender, DataTableClearEventArgs e)
        {
            if (LogChanged != null)
                LogChanged(this, new EventArgs());
        }

        void validationLog_LogRowChanged(object sender, ValidationLog.LogRowChangeEvent e)
        {
            if (LogChanged != null)
                LogChanged(this, new EventArgs());
        }

        /// <summary>
        /// this is the worker for loading a document the 
        /// </summary>
        private void LoadDocument(string xeml, bool asTemplate)

        {
            try
            {
                if (this.CurrentXemlDoc != null)
                {
                    this.CurrentXemlDoc.Validation -= OnXemlDocValidation;
                    this.CurrentXemlDoc.Changed -= OnCurrentXemlDocChanged;
                }

                this.UnsavedChanges = false;
                this.CurrentXemlDoc = new Xeml.Document.XemlDocument();

                SetXemlCode(xeml, false, false);

                this.CurrentXemlDoc.Validation += OnXemlDocValidation;
                //SetDesignMode();

                if (asTemplate)
                {
                    this.CurrentXemlDoc.NewId();
                    var now = DateTime.Now;

                    this.CurrentXemlDoc.StartDate = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0);
                    //this.CurrentXemlDoc.Name = "New Experiment";
                    this.FilePath = "";
                    if (this.FilePathChanged != null)
                        this.FilePathChanged(this, new EventArgs());
                }

#if OFFLINE
                    foreach (var item in this.currentXemlDoc.DocumentResources.EnvironmentOntologyHandler)
                    {
                        if (item.Location.ToLower().EndsWith("XeO_V1.xml".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\XeO_V1.xml";
                        if (item.Location.ToLower().EndsWith("recent".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\XeO_V1.xml";
                        if (item.Location.ToLower().EndsWith("XEOintern_V1.xml".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\XEOintern_V1.xml";
                        if (item.Location.ToLower().EndsWith("obo".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\PO.obo";

                    }

                    foreach (var item in this.currentXemlDoc.DocumentResources.OrganismStructureOntologyHandler)
                    {

                        if (item.Location.ToLower().Contains("po_anatomy.obo".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\PO.obo";
                    }
#endif

                SampleIndexer.Instance.Init(this.CurrentXemlDoc);
                XemlCore.Indexing.ObservationIndexer.Instance.Init(this.CurrentXemlDoc);
                OnFileLoaded();

                this.CurrentXemlDoc.Changed += OnCurrentXemlDocChanged;

                //if (FileSaved != null)
                //    FileSaved(this, new EventArgs());
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// Loads a document from a given path.
        /// </summary>
        /// <param name="path">The path to the document.</param>
        /// <param name="asTemplate">Indicates wether the document should be handled as template or not.</param>
        public void LoadFile(string @path, bool asTemplate)
        {
            OnBackgroundWorkStarted("Loading \"" + path + "\"");
            try
            {
                if (File.Exists(path))
                {
                    LoadDocument(LoadXemlCodeFromFile(path), asTemplate);
                }
                else
                {
                    MessageBox.Show("File " + path + " not found", "File not found", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            finally
            {
                OnBackgroundWorkEnded();
            }
        }

        public void LoadXeml(string xeml, bool asTemplate)
        {
            OnBackgroundWorkStarted("Loading document");
            try
            {
                LoadDocument(xeml, asTemplate);
            }
            finally
            {
                OnBackgroundWorkEnded();
            }
        }

        public void PurgeDetailsFromDocument()
        {
            OnBackgroundWorkStarted("Purging document details");
            try
            {
                (CurrentXemlDoc as XemlDocument).Purge();
            }
            finally
            {
                OnBackgroundWorkEnded();
            }
        }  

        /// <summary>
        /// Creates a new xeml document.
        /// </summary>
        public void New()
        {
            OnBackgroundWorkStarted("Creating new document.");
            //try
            //{
                if (CurrentXemlDoc != null)
                {
                    this.CurrentXemlDoc.Validation -= OnXemlDocValidation;
                    this.CurrentXemlDoc.Changed -= OnCurrentXemlDocChanged;
                }

                string path = Path.Combine(Properties.Settings.Default.TemplateFolder, "Standard.xeml");
                if (File.Exists(path))
                {
                    LoadFile(path, true);
                }
                else
                {
                    this.HasErrors = false;
                    this.InvalidDocument = false;
                    this.CurrentXemlDoc = new Xeml.Document.XemlDocument();
                    this.CurrentXemlDoc.Validation += new EventHandler<ValidationMessage>(OnXemlDocValidation);
                    this.CurrentXemlDoc.Changed += new EventHandler(OnCurrentXemlDocChanged);
                    this.CurrentXemlDoc.RefreshXeml();
                    SetXemlCode(this.CurrentXemlDoc.Xeml, false, true);
                    //SetDesignMode();
                    //this.currentXemlDoc.SchemaLocation = Application.StartupPath + "\\XEMLSchema_v2.xsd";
                    this.FilePath = "";

#if OFFLINE

                    foreach (var item in this.currentXemlDoc.DocumentResources.EnvironmentOntologyHandler)
                    {
                        if (item.Location.ToLower().EndsWith("XeO_V1.xml".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\XeO_V1.xml";
                        if (item.Location.ToLower().EndsWith("recent".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\XeO_V1.xml";
                        if (item.Location.ToLower().EndsWith("XEOintern_V1.xml".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\XEOintern_V1.xml";
                  

                    }
                    foreach (var item in this.currentXemlDoc.DocumentResources.OrganismStructureOntologyHandler)
                    {

                        if (item.Location.ToLower().Contains("po_anatomy.obo".ToLower()))
                            item.Location = @"file://" + Application.StartupPath + @"\PO.obo";

                    }

                   
#endif

                    this.UnsavedChanges = false;
                    OnFileLoaded();
                    if (FileSaved != null)
                        FileSaved(this, new EventArgs());
                    //this.currentXemlDoc.Changed += new EventHandler(OnCurrentXemlDocChanged);

                    SampleIndexer.Instance.Init(this.CurrentXemlDoc);
                    IndividualIndexer.Instance.Init(this.CurrentXemlDoc);
                    XemlCore.Indexing.ObservationIndexer.Instance.Init(this.CurrentXemlDoc);
                }
//            }
//#if DEBUG
//            catch (Exception ex)
//            {
//                Debugger.Break();
//            }
//#endif
//            finally
//            {
//                OnBackgroundWorkEnded();
//            }
        }

        /// <summary>
        /// Saves the current xeml document,
        /// </summary>
        /// <param name="path">The path to the target file.</param>
        public void SaveDocument(string @path)
        {
            string savePath = path;

            try
            {
                if (String.IsNullOrEmpty(savePath) && FilePathNeeded != null)
                {
                    FilePathNeededEventArgs fpn = new FilePathNeededEventArgs();
                    FilePathNeeded(this, fpn);
                    if (fpn.Cancel)
                        return;

                    savePath = fpn.Path;
                }

                this.CurrentXemlDoc.Save(savePath);

                if (this.FilePath != savePath)
                {
                    this.FilePath = savePath;
                    if (FilePathChanged != null)
                        FilePathChanged(this, new EventArgs());
                }

                this.UnsavedChanges = false;
                if (FileSaved != null)
                    FileSaved(this, new EventArgs());
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


        /// <summary>
        /// Saves the current xeml document to the current path.
        /// </summary>
        public void SaveDocument()
        {
            SaveDocument(this.FilePath);
        }

        /// <summary>
        /// Saves the current xeml document to the current path.
        /// </summary>
        public void SaveDocumentAs()
        {
            if (FilePathNeeded != null)
            {
                FilePathNeededEventArgs fpn = new FilePathNeededEventArgs();
                fpn.Path = this.FilePath;
                FilePathNeeded(this, fpn);
                if (fpn.Cancel)
                    return;
                else
                    SaveDocument(fpn.Path);
            }

        }

        /// <summary>
        /// Loads a xeml document.
        /// </summary>
        /// <param name="path">The path to the xeml file.</param>
        /// <returns></returns>
        private string LoadXemlCodeFromFile(string path)
        {
            this.FilePath = path;
            if (FilePathChanged != null)
                FilePathChanged(this, new EventArgs());
            return File.ReadAllText(path);
        }


        /// <summary>
        /// Set's xeml source code to the current document.
        /// </summary>
        /// <param name="xeml">The xeml source code.</param>
        /// <remarks>The usage alters the manager into the source code mode</remarks>
        public void SetXemlCode(string xeml, bool raiseSomethingChangedEvent, bool doValidation)
        {
            this.CurrentXemlDoc.Load(xeml);
            
            if (doValidation)
                DoValidation(false);

            if (!UnsavedChanges && raiseSomethingChangedEvent && SomethingChanged != null)
            {
                this.UnsavedChanges = true;
                SomethingChanged(this, new EventArgs());
            }
        }

        private void InitValidator()
        {
            this.validators = new List<IValidator>();
            foreach (string uri in ValidatorManager.Instance.ValidationComponents.Keys)
            {
                IValidator val = ValidatorManager.Instance.CreateValidator(uri);
                this.validators.Add(val);
                val.Validation += new EventHandler<ValidationMessage>(OnXemlDocValidation);
            }
        }

        public void DoValidation(bool withOntolgies)
        {

            ResetValidation(false);
            this.CurrentXemlDoc.DoXmlValidation();

            foreach (IValidator iv in this.validators)
            {
                iv.Validate(this.CurrentXemlDoc);
            }

            //if (withOntolgies)
            //{
            //    foreach (OntologyHandlerResource pvr in currentXemlDoc.DocumentResources.EnvironmentOntologyHandler)
            //    {
            //        if (pvr.ComponentState == ComponentLoadState.Loaded)
            //        {
            //            pvr.Handler.Validation += new EventHandler<ValidationMessage>(OnXemlDocValidation);
            //            pvr.Handler.Validate(this.currentXemlDoc);
            //            pvr.Handler.Validation -= new EventHandler<ValidationMessage>(OnXemlDocValidation);
            //        }
            //    }
            //}
        }

        private void ResetValidation(bool holdXemlDocMsgs)
        {
            this.HasErrors = false;
            this.InvalidDocument = false;

            List<DataRow> toRem = new List<DataRow>();

            if (holdXemlDocMsgs)
            {
                foreach (DataRow row in ValidationLog.Select("Source <> 'XemlDocument'"))
                {
                    ValidationLog.Rows.Remove(row);
                }
            }
            else
                ValidationLog.Rows.Clear();
        }

        #region Event- /handling

        public event EventHandler FilePathChanged;
        public event EventHandler FileLoaded;
        public event EventHandler FileSaved;
        public event EventHandler SomethingChanged;
        public event EventHandler EditModeChanged;
        public event EventHandler DesignModeRejected;
        public event EventHandler<FilePathNeededEventArgs> FilePathNeeded;
        public event EventHandler LogChanged;
        public event EventHandler ValidationStarted;
        public event EventHandler<UndoRedoAvailabilityEventArgs> UndoAvailabilityChanged;
        public event EventHandler<UndoRedoAvailabilityEventArgs> RedoAvailabilityChanged;

        public event EventHandler<BusyEventArgs> BackgroundWorkStarted;
        public event EventHandler BackgroundWorkEnded;

        protected virtual void OnBackgroundWorkStarted(string message)
        {
            if (BackgroundWorkStarted != null)
                BackgroundWorkStarted(this, new BusyEventArgs(message));
        }

        protected virtual void OnBackgroundWorkEnded()
        {
            if (BackgroundWorkEnded != null)
                BackgroundWorkEnded(this, new EventArgs());
        }



        protected virtual void OnFileLoaded()
        {
            _undoStack.Clear();
            _redoStack.Clear();

            _undoStack.Push(CurrentXemlDoc.Xeml);
            if (FileLoaded != null)
                FileLoaded(this, new EventArgs());

            if (UndoAvailabilityChanged != null)
                UndoAvailabilityChanged(this, new UndoRedoAvailabilityEventArgs(false));
            if (RedoAvailabilityChanged != null)
                RedoAvailabilityChanged(this, new UndoRedoAvailabilityEventArgs(false));
        }

        void OnXemlDocValidation(object sender, ValidationMessage e)
        {
            IValidator iv = sender as IValidator;

            ValidationLog.LogRow lr = ValidationLog.NewLogRow();
            switch (e.SeverityCode)
            {
                case SeverityCode.Error:
                    {
                        this.HasErrors = true;
                        lr.SeverityCode = "Error";
                        break;
                    }
                case SeverityCode.Message:
                    {
                        lr.SeverityCode = "Info";
                        break;
                    }
                case SeverityCode.Warning:
                    {
                        lr.SeverityCode = "Warning";
                        break;
                    }
            }

            lr.Message = e.Text;
            if (e.RelatedObject != null)
                lr.RelatedObject = e.RelatedObject.ToString();
            //string[] className = sender.ToString().Split('.');
            //  if (e.RelatedObject != null)


            lr.Source = sender.GetType().Name;// className[className.Length - 1];



            if (e.InhibitRendering)
            {
                this.InvalidDocument = true;
                lr.DocumentError = true;
            }
            else
            {
                lr.DocumentError = false;
            }

            ValidationLog.Rows.Add(lr);
        }

        void OnCurrentXemlDocChanged(object sender, EventArgs e)
        {
            Debug.WriteLine("FileMgr: Doc changed");
            _timer.Stop();


            //ResetValidation(true);
            //DoValidation();


            _timer.Start();
        }
        #endregion


    }

    /// <summary>
    /// A edit mode enumeration.
    /// </summary>
    internal enum EditMode
    {
        DesignMode,
        CodeMode
    }

    /// <summary>
    /// Event args for requesting a file path.
    /// </summary>
    public class FilePathNeededEventArgs : EventArgs
    {
        private string path;
        private bool cancel = false;

        public bool Cancel
        {
            get { return cancel; }
            set { cancel = value; }
        }


        public string Path
        {
            get { return path; }
            set { path = value; }
        }

    }

    public class UndoRedoAvailabilityEventArgs : EventArgs
    {
        private bool _available;
        public bool Available
        {
            get { return _available; }
        }

        public UndoRedoAvailabilityEventArgs(bool avail)
        {
            _available = avail;
        }
    }
}
